The sample program presented in this section illustrates the processes described in the previous sections. The program has been divided into several functions. Listing 3-2 shows the main program.
Listing 2Compressing and decompressing a sequence of images: The main program
WindowPtr displayWindow; /* window in which to display
sequence */
Rect windowRect; /* rectangle of displayWindow */
main (void)
{
WindowPtr displayWindow;
Rect windowRect;
InitGraf (&thePort);
InitFonts ();
InitWindows ();
InitMenus ();
TEInit ();
InitDialogs (nil);
SetRect (&windowRect, 0, 0, 256, 256);
OffsetRect (&windowRect,/* middle of screen */
((qd.screenBits.bounds.right - qd.screenBits.bounds.left) -
windowRect.right) / 2,
((qd.screenBits.bounds.bottom - qd.screenBits.bounds.top) -
windowRect.bottom) / 2);
displayWindow = NewCWindow (nil, &windowRect,
"\pImage", true, 0,
(WindowPtr)-1, true, 0);
if (displayWindow)
{
SetPort (displayWindow);
SequenceSave ();
SequencePlay ();
}
}
The SequenceSave function shown in Listing 3 saves a sequence of images to a disk file. This function creates and opens a disk file for the image sequence, calls the CompressSequence function to create and compress the image sequence into the file, and then calls the MakeMyResource function to save the image description resource in the file, so that the sequence can be played back later. For details on CompressSequence , see the next section.
The data for each frame is written to the data fork of the disk file, preceded by a long word that contains the number of bytes of data for that frame. A description of the compressed images in the sequence is stored in a 'SEQU' resource in the same file with a resource ID of 128 or 129. This description is simply the image description structure maintained by the Image Compression Manager.
The image for each frame of the sequence is drawn into an offscreen graphics world that the SequenceSave function creates in the currWorld variable. SequenceSave calls the DrawOneFrame function (described in the next section) to draw each frame's image into the currWorld variable. Before any of the frames of the sequence are drawn, the Image Compression Manager is prepared to compress a sequence of images through the CompressSequence function.
Compressing and decompressing a sequence of images: Saving a sequence to a disk file
void SequenceSave (void)
{
long filePos;
StandardFileReply fileReply;
short dfRef = 0;
OSErr error;
ImageDescriptionHandle description = nil;
StandardPutFile ("\p", "\pSequence File", &fileReply);
if (fileReply.sfGood)
{
if (! (fileReply.sfReplacing))
{
error = FSpCreate (&fileReply.sfFile, 'SEQM', 'SEQU',
fileReply.sfScript);
CheckError (error, "\pFSpCreate");
}
error = FSpOpenDF (&fileReply.sfFile, fsWrPerm, &dfRef);
CheckError (error, "\pFSpOpenDF");
error = SetFPos (dfRef, fsFromStart, 0);
CheckError (error, "\pSetFPos");
CompressSequence (&dfRef, &description);
error = GetFPos (dfRef, &filePos);
CheckError (error, "\pGetFPos");
error = SetEOF (dfRef, filePos);
CheckError (error, "\pSetEOF");
FSClose (dfRef);
FlushVol (nil, fileReply.sfFile.vRefNum);
MakeMyResource (fileReply, description);
if (description != nil)
DisposeHandle ((Handle) description);
}
}
void MakeMyResource ( StandardFileReply fileReply,
ImageDescriptionHandle description)
{
OSErr error;
short rfRef;
Handle sequResource;
FSpCreateResFile (&fileReply.sfFile, 'SEQM', 'SEQU',
fileReply.sfScript);
error = ResError();
if (error != dupFNErr)
CheckError (error, "\pFSpCreateResFile");
rfRef = FSpOpenResFile (&fileReply.sfFile, fsRdWrPerm);
CheckError (ResError (), "\pFSpOpenResFile");
SetResLoad (false);
sequResource = Get1Resource ('SEQU', 128);
if (sequResource)
RmveResource (sequResource);
SetResLoad (true);
sequResource = (Handle) description;
error = HandToHand (&sequResource);
CheckError (error, "\pHandToHand");
AddResource (sequResource,'SEQU', 128, "\p");
CheckError (ResError (), "\pAddResource");
UpdateResFile (rfRef);
CheckError (ResError (), "\pUpdateResFile");
CloseResFile (rfRef);
}
Listing 4 shows the CompressSequence function, which creates and then compresses the image sequence. CompressSequenceBegin informs the Image Compression Manager which compressor (of type codectype ) to use, what the desired compression quality is, the key frame rate, the portion of the image to compress (in this example, the entire image is compressed), and the image to be compressed (in this example, the pixel map [of type PixMap ] in the currWorld variable).
CompressSequenceBegin returns a unique number that identifies the sequence for subsequent image-compression routines, and it initializes a new image description structure, which is stored in the handle referenced by the description local variable.
Using a loop, the DrawOneFrame function draws each frame until the last frame is drawn, at which time the function returns the value of false . Each frame that it draws is copied to the window so that it can be seen during the compression sequence.
The CompressSequenceFrame function is used to compress each frame's image. CompressSequenceFrame tells the Image Compression Manager
In updating the previous frame's buffer for frame differencing, the Image Compression Manager control flag codecFlagUpdatePrevious copies the uncompressed image to the previous frame's buffer; contrast this with the codecFlagUpdatePreviousComp flag, which copies the compressed image to the previous frame's buffer--the more lossy the compression, the more the difference between the compressed and uncompressed images.
The CompressSequenceBegin function returns a rating of the similarity between the current frame and the previous frame, but this example ignores this rating. After each frame is compressed, the number of bytes in the compressed image data is written to the disk file, followed by the compressed image data itself.
After all the images in the sequence have been compressed, the CDSequenceEnd function is called to tell the Image Compression Manager that the sequence is over. The data fork of the file is closed, and the image description is written to a 'SEQU' resource.
The DrawOneFrame function draws one frame of the sequence with QuickDraw. The frame's image is drawn into the rectangle specified by the destRect parameter. The image is a set of color ramps in which the shading goes from light to dark in smooth increments. The color ramps fill the destination rectangle and the current frame number centered within the destination rectangle over the ramps.
The PaintImage function paints a series of vertical color ramps into the rectangle specified by the destRect parameter into the current color graphics port. This is done through a nested loop. The outer loop iterates only twice, and half of the ramps are drawn in the first iteration and half in the second. The inner loop iterates over all the steps in a ramp.
Compressing and decompressing a sequence of images: Drawing one frame with QuickDraw
void CompressSequence (short* dfRef,
ImageDescriptionHandle* description)
{
GWorldPtr currWorld = nil;
PixMapHandle currPixMap;
CGrafPtr savedPort;
GDHandle savedDevice;
Handle buffer = nil;
Ptr bufferAddr;
long compressedSize;
long dataLen;
Rect imageRect;
ImageSequence sequenceID = 0;
short frameNum;
OSErr error;
CodecType codecKind = 'rle ';
GetGWorld (&savedPort, &savedDevice);
imageRect = savedPort->portRect;
error = NewGWorld (&currWorld, 32, &imageRect, nil, nil, 0);
CheckError (error, "\pNewGWorld");
SetGWorld (currWorld, nil);
currPixMap = currWorld->portPixMap;
LockPixels (currPixMap);
/*
Allocate an embryonic image description structure and the
Image Compression Manager will resize.
*/
*description = (ImageDescriptionHandle) NewHandle (4);
error = CompressSequenceBegin (
&sequenceID,
currPixMap,
nil, /* tell ICM to allocate previous
image buffer */
&imageRect,
&imageRect,
0, /* let ICM choose pixel depth */
codecKind,
(CompressorComponent) anyCodec,
codecNormalQuality, /* spatial quality */
codecNormalQuality, /* temporal quality */
5, /* at least 1 key frame every
5 frames */
nil, /* use default color table */
codecFlagUpdatePrevious,
*description );
CheckError (error, "\pCompressSequenceBegin");
error = GetMaxCompressionSize(
currPixMap,
&imageRect,
0, /* let ICM choose pixel depth */
codecNormalQuality, /* spatial quality */
codecKind,
(CompressorComponent) anyCodec,
&compressedSize );
CheckError (error, "\pGetMaxCompressionSize");
buffer = NewHandle(compressedSize);
CheckError (MemError(), "\pNewHandle buffer");
MoveHHi (buffer);
HLock (buffer);
bufferAddr = StripAddress (*buffer);
for (frameNum = 1; frameNum <= 10; frameNum++)
{
DrawFrame (&imageRect, frameNum);
error = CompressSequenceFrame (
sequenceID,
currPixMap,
&imageRect,
codecFlagUpdatePrevious,
bufferAddr,
&compressedSize,
nil,
nil );
CheckError (error, "\pCompressSequenceFrame");
dataLen = 4;
error = FSWrite (*dfRef, &dataLen, &compressedSize);
CheckError (error, "\pFSWrite length");
error = FSWrite (*dfRef, &compressedSize, bufferAddr);
CheckError (error, "\pFSWrite buffer");
}
CDSequenceEnd (sequenceID);
DisposeGWorld (currWorld);
SetGWorld (savedPort,savedDevice);
if (buffer) DisposeHandle ( buffer );
}
void DrawFrame (const Rect *imageRect, long frameNum)
{
Str255 numStr;
ForeColor( redColor );
PaintRect( imageRect );
ForeColor( blueColor );
NumToString (frameNum, numStr);
MoveTo ( imageRect->right / 2, imageRect->bottom / 2);
TextSize ( imageRect->bottom / 3);
DrawString (numStr);
}
The SequencePlay function, shown in Listing 5 , plays back a sequence of images from a disk file that was created by the SequenceSave function (see Listing 3 for details).
The SequencePlay function begins by grabbing the image description structure from the file that the user specified from a 'SEQU' resource ID 128. This structure is needed to decompress the images in the file.
Before these compressed images are read, the Image Compression Manager is told to prepare to decompress a sequence of images through the DecompressSequenceBegin function. This routine tells the Image Compression Manager
A loop iterates for each frame in the file. For each frame, a long word with the number of bytes in the frame is read from the file, and then that many bytes are read from the file into a compressed-image buffer. This buffer is passed to DecompressSequenceFrame , which decompresses the image to the screen (the destination doesn't have to be the screen, but it is in this example). The loop iterates until the end of the file has been reached.
Compressing and decompressing a sequence of images: Decompressing and playing back a sequence from a disk file
void SequencePlay (void)
{
ImageDescriptionHandle description;
long compressedSize;
Handle buffer = nil;
Ptr bufferAddr;
long dataLen;
long lastTicks;
ImageSequence sequenceID;
Rect imageRect;
StandardFileReply fileReply;
SFTypeList typeList = {'SEQU',0,0,0};
short dfRef = 0; /* sequence data fork */
short rfRef = 0; /* sequence resource fork */
OSErr error;
StandardGetFile (nil, 1, typeList, &fileReply);
if (!fileReply.sfGood) return;
rfRef = FSpOpenResFile (&fileReply.sfFile, fsRdPerm);
CheckError (ResError (), "\pFSpOpenResFile");
description = (ImageDescriptionHandle)
Get1Resource ('SEQU', 128);
CheckError (ResError (), "\pGet1Resource");
DetachResource ((Handle) description );
HNoPurge ((Handle) description );
CloseResFile (rfRef);
error = FSpOpenDF (&fileReply.sfFile, fsRdPerm, &dfRef);
CheckError (error, "\pFSpOpenDF");
buffer = NewHandle (4);
CheckError (MemError (), "\pNewHandle buffer");
SetRect (&imageRect, 0, 0, (**description).width,
(**description).height);
error = DecompressSequenceBegin (
&sequenceID,
description,
nil, /* use the current port */
nil, /* go to screen */
&imageRect,
nil, /* no matrix */
ditherCopy,
nil, /* no mask region */
codecFlagUseImageBuffer,
codecNormalQuality, /* accuracy */
(CompressorComponent) anyCodec);
while (true)
{
dataLen = 4;
error = FSRead (dfRef, &dataLen, &compressedSize);
if (error == eofErr)
break;
CheckError( error, "\pFSRead" );
if (compressedSize > GetHandleSize (buffer))
{
HUnlock (buffer);
SetHandleSize (buffer, compressedSize);
CheckError (MemError(), "\pSetHandleSize");
}
HLock (buffer);
bufferAddr = StripAddress (*buffer);
error = FSRead (dfRef, &compressedSize, bufferAddr);
CheckError (error, "\pFSRead");
error = DecompressSequenceFrame (
sequenceID,
bufferAddr,
0, // flags
nil,
nil );
CheckError (error, "\pDecompressSequenceFrame");
Delay (30, &lastTicks);
}
CDSequenceEnd (sequenceID);
if (dfRef) FSClose (dfRef);
if (buffer) DisposeHandle (buffer);
if (description) DisposeHandle ((Handle)description);
}